package com.iwdnb.gkgz.application.strategy;

import java.math.BigDecimal;
import java.util.ArrayList;
import java.util.Date;
import java.util.List;
import java.util.Objects;

import com.alibaba.fastjson.JSON;

import cn.hutool.core.date.DateUtil;
import com.iwdnb.bmnf.common.utils.BooleanUtils;
import com.iwdnb.gkgz.application.model.request.AddStrategyTradeStockDataRequest;
import com.iwdnb.gkgz.common.model.dto.StockDayData;
import com.iwdnb.gkgz.common.model.dto.StockRangeDTO;
import com.iwdnb.gkgz.common.model.dto.StockRangeScopeDTO;
import com.iwdnb.gkgz.common.model.dto.StrategyTradeDTO;
import com.iwdnb.gkgz.common.quota.BollingerBands;
import com.iwdnb.gkgz.common.quota.BollingerBands.BollingerData;
import com.iwdnb.gkgz.common.quota.MovingAverage;
import com.iwdnb.gkgz.common.utils.BigDecimalUtils;
import com.iwdnb.gkgz.common.utils.DateUtils;
import com.iwdnb.gkgz.common.utils.StockRangeUtils;
import com.iwdnb.gkgz.common.utils.StockUtils;
import com.iwdnb.wwzy.model.query.MetaQuery;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.collections4.CollectionUtils;
import org.apache.commons.lang3.StringUtils;
import org.springframework.stereotype.Service;

import static com.iwdnb.gkgz.common.constant.GkgzConstants.OBJECT_CODE_STRATEGY_TRADE;
import static com.iwdnb.gkgz.common.utils.StockUtils.getAveragePrice;

@Service
@Slf4j
public class LowRangeStockBackValidateService extends CommmonStockBackValidateService {

    private static final String STATEGY_CODE = "lowRange";

    @Override
    public List<StrategyTradeDTO> queryBackValidateDateList(String code, String strategy,
        List<StockDayData> stockDayDataList, AddStrategyTradeStockDataRequest request) {
        if (request.isCacheFlag()) {
            MetaQuery metaQuery = new MetaQuery();
            metaQuery.addParam("code", code);
            metaQuery.addParam("uuid", STATEGY_CODE);
            List<StrategyTradeDTO> list = metaDataDAO.queryListByCondition(OBJECT_CODE_STRATEGY_TRADE, metaQuery,
                StrategyTradeDTO.class);
            if (CollectionUtils.isNotEmpty(list)) {
                return new ArrayList<>();
            }
        }
        if (BooleanUtils.isFalse(request.getGenerateFlag())) {
            MetaQuery metaQuery = new MetaQuery();
            metaQuery.addParam("code", code);
            metaQuery.addParam("uuid", STATEGY_CODE);
            return metaDataDAO.queryListByCondition(OBJECT_CODE_STRATEGY_TRADE, metaQuery,
                StrategyTradeDTO.class);
        }
        int twentyPeriod = 20;
        //int thirtyPeriod = 30;
        int sixtyPeriod = 60;
        MovingAverage.calculateEMA(stockDayDataList, twentyPeriod);
        //MovingAverage.calculateEMA(stockDayDataList, thirtyPeriod);
        MovingAverage.calculateEMA(stockDayDataList, sixtyPeriod);
        //BollingerBands.calculateBollingerBands(stockDayDataList);
        List<String> dateList = new ArrayList<>();
        List<StrategyTradeDTO> strategyTradeDTOList = new ArrayList<>();
        List<StockRangeDTO> rangeDTOList = getRangeList(code, stockDayDataList);
        BigDecimal EIGHTEEN = new BigDecimal(18);

        BigDecimal NEG_FIVE = new BigDecimal(-5);
        int matchIndex = 200;
        BigDecimal FIVE = new BigDecimal(5);
        for (int i = 250; i < stockDayDataList.size(); i++) {
            if (i - matchIndex < 30) {
                continue;
            }
            StockDayData data = stockDayDataList.get(i);
            String date = data.getDate();
            Date currentDate = DateUtil.parseDate(date);
            //在20日均线下方
            BigDecimal twentyAvg = StockUtils.getAveragePrice(data, twentyPeriod);
            if (BigDecimalUtils.isGt(data.getClosePrice(), twentyAvg)) {
                continue;
            }
            //与20日前的60日均线差价不超过5个点
            StockDayData twentyDayData = StockUtils.getStockDayDataBeforeDate(stockDayDataList, date, 20);
            BigDecimal twentyDaySixtyAvg = StockUtils.getAveragePrice(twentyDayData, sixtyPeriod);
            BigDecimal sixtyAvg = StockUtils.getAveragePrice(data, sixtyPeriod);
            BigDecimal sixtyRate = BigDecimalUtils.subStractAndDividePrecent(twentyDaySixtyAvg, sixtyAvg).abs();
            if (BigDecimalUtils.isGt(sixtyRate, FIVE)) {
                continue;
            }
            //4个月下跌超过20%
            String beginDateStr = DateUtil.formatDate(DateUtil.offsetDay(currentDate, -180));
            String endDateStr = DateUtil.formatDate(DateUtil.offsetDay(currentDate, -5));
            StockRangeScopeDTO twoMonthRangeScope = StockUtils.getRangeScopeByDateRange(stockDayDataList, beginDateStr,
                endDateStr);
            if (Objects.isNull(twoMonthRangeScope)) {
                continue;
            }
            BigDecimal highRate = BigDecimalUtils.subStractAndDividePrecent(twoMonthRangeScope.getHighPrice(),
                data.getClosePrice());
            if (BigDecimalUtils.isLe(highRate, EIGHTEEN)) {
                continue;
            }
            //股价需为20天内最低价
            BigDecimal lowPrice = StockUtils.getMinPriceBeforeDate(stockDayDataList, date, 20);
            if (BigDecimalUtils.isLt(lowPrice, data.getMinPrice())) {
                continue;
            }
            BigDecimal lowRate = BigDecimalUtils.subStractAndDividePrecent(twoMonthRangeScope.getLowPrice(),
                data.getClosePrice());
            if (BigDecimalUtils.isLe(lowRate, NEG_FIVE)) {
                continue;
            }
            Date endDate = DateUtil.parseDate(twoMonthRangeScope.getLowDate());
            //低价日期在15天之内,当成未匹配
            long timeRange = currentDate.getTime() - endDate.getTime();
            if (timeRange <= 86400000 * 15) {
                continue;
            }
            StockRangeDTO lowRange = StockRangeUtils.getMatchLowRangeByDateRange(data, rangeDTOList);
            if (Objects.isNull(lowRange)) {
                continue;
            }
            BigDecimal lowLimitPrice = lowRange.getLowPrice();
            //止损价格为低价波段最低价
            BigDecimal lowRangeRate = BigDecimalUtils.subStractAndDividePrecent(data.getClosePrice(), lowLimitPrice);
            //股价在低价波段1个点以内时,止损价为6个点
            if (BigDecimalUtils.isLt(lowRangeRate, BigDecimal.ONE)) {
                lowLimitPrice = BigDecimalUtils.multiply(data.getClosePrice(), new BigDecimal(0.94));
            }
            //20天之内未跌破最低价3个点
            List<StockDayData> subList = StockUtils.getStockDayDataListAfterDate(stockDayDataList, date, 20);
            if (CollectionUtils.isEmpty(subList)) {
                continue;
            }
            int tradeNum = (int)(data.getTradeNum() * 1.5);
            boolean findMatch = true;
            for (int j = 0; j < subList.size(); j++) {
                StockDayData subData = subList.get(j);
                BigDecimal rate = BigDecimalUtils.subStractAndDividePrecent(subData.getClosePrice(),
                    lowRange.getLowPrice());
                //成交放量上涨,即为买入信号
                if (BigDecimalUtils.isGt(subData.getRate(), new BigDecimal(5))
                    && subData.getTradeNum() > tradeNum) {
                    log.debug("{}-{}-{}放量上涨,买入", code, date, data.getClosePrice());
                    data = subData;
                    date = data.getDate();
                    break;
                } else if (BigDecimalUtils.isLt(rate, new BigDecimal(-1))) {
                    log.debug("{}-{}-{}跌破波段最低价{},跌破比例:{},不买了", code, date, data.getClosePrice(),
                        lowRange.getLowPrice(), rate);
                    findMatch = false;
                    break;
                }
                if (j == subList.size() - 1) {
                    findMatch = false;
                }
            }
            matchIndex = i;
            if (findMatch && !dateList.contains(date)) {
                //BollingerData bollingerData = data.getBollingerBand();
                //log.info("{}-{}-{},rate:{},bollingerData:{}", code, date, data.getClosePrice(), data.getRate(),
                //    JSON.toJSONString(bollingerData));
                dateList.add(date);
                StrategyTradeDTO strategyTradeDTO = new StrategyTradeDTO();
                strategyTradeDTO.setCode(code);
                strategyTradeDTO.setUuid(STATEGY_CODE);
                strategyTradeDTO.setBuyDate(DateUtil.parseDate(date));
                strategyTradeDTO.setBuyPrice(data.getClosePrice());
                strategyTradeDTO.setStatus("buy");
                strategyTradeDTO.setRealtimePrice(lowLimitPrice);
                strategyTradeDTO.setSummary(JSON.toJSONString(lowRange));
                strategyTradeDTOList.add(strategyTradeDTO);
            }

        }
        return strategyTradeDTOList;
    }

    @Override
    public void doBackValidate(String code, String strategy,
        List<StockDayData> stockDayDataList, List<StrategyTradeDTO> strategyTradeDTOList) {
        Date latestEndDate = strategyTradeDTOList.get(0).getBuyDate();
        latestEndDate = DateUtil.offsetDay(latestEndDate, -1);
        for (StrategyTradeDTO strategyTradeDTO : strategyTradeDTOList) {
            Date d = strategyTradeDTO.getBuyDate();
            if (DateUtils.beforeOrEquals(d, latestEndDate)) {
                log.debug("{}-{}在上一个周期内,忽略", code, d);
                continue;
            }
            //单交易回测
            doBackTradeInfo(code, stockDayDataList, strategyTradeDTO);
            latestEndDate = strategyTradeDTO.getSellDate();
        }
    }

    private void doBackTradeInfo(String code, List<StockDayData> stockDayDataList,
        StrategyTradeDTO strategyTradeDTO) {
        String date = DateUtil.formatDate(strategyTradeDTO.getBuyDate());
        StockDayData buyDayData = StockUtils.getStockDayData(stockDayDataList, date);
        BigDecimal buyPrice = buyDayData.getClosePrice();
        List<StockDayData> subList = StockUtils.getStockDayDataListAfterDate(stockDayDataList, date, 30);
        if (CollectionUtils.isEmpty(subList) || subList.size() < 10) {
            return;
        }
        int size = subList.size();
        BigDecimal closeRate = null;
        String sellDate = null;
        String info = "";
        BigDecimal sellPrice = null;
        BigDecimal rate = closeRate;
        int holdIndex = 0;
        BigDecimal closePrice = null;
        BigDecimal rangeMaxPrice = strategyTradeDTO.getBuyPrice();
        BigDecimal rangeMaxRate = BigDecimal.ZERO;

        BigDecimal FIFTEEN = new BigDecimal(15);
        BigDecimal THIRTY = new BigDecimal(30);
        BigDecimal FOURTY = new BigDecimal(40);

        BigDecimal lowLimitPrice = strategyTradeDTO.getRealtimePrice();
        for (int i = 0; i < subList.size(); i++) {
            StockDayData data = subList.get(i);
            StockDayData yesterdayDayData = i == 0 ? data : subList.get(i - 1);
            closePrice = data.getClosePrice();
            closeRate = BigDecimalUtils.subStractAndDividePrecent(closePrice, buyPrice);
            sellDate = data.getDate();
            sellPrice = data.getClosePrice();
            holdIndex = i;
            BigDecimal upRate = BigDecimalUtils.subStractAndDividePrecent(data.getMaxPrice(), buyPrice);
            BigDecimal openRange = data.getOpenPrice().subtract(buyPrice);
            BigDecimal openRate = BigDecimalUtils.divideToPrecent(openRange, buyPrice);
            rangeMaxPrice = BigDecimalUtils.isGt(data.getClosePrice(), rangeMaxPrice) ? data.getClosePrice()
                : rangeMaxPrice;
            rangeMaxRate = BigDecimalUtils.subStractAndDividePrecent(rangeMaxPrice, buyPrice);
            BigDecimal realtimeRate = BigDecimalUtils.subStractAndDividePrecent(data.getClosePrice(), buyPrice);
            BigDecimal fallBackRate = BigDecimal.ZERO;
            if (BigDecimalUtils.isGt(realtimeRate, BigDecimal.ZERO)) {
                fallBackRate = BigDecimalUtils.subStractAndDividePrecent(rangeMaxPrice, data.getClosePrice());
            }
            rate = closeRate;
            String sellInfo = "收盘价卖出";
            // 跌5个点，跑路
            if (BigDecimalUtils.isLt(closeRate, new BigDecimal(-5))) {
                sellInfo = "跌了超过5个点,止损";
                log.debug("第{}天-{}-{}-{}，收益={}，开盘收益={},收盘收益={},最高点收益={}", (i + 1), code, data.getDate(), sellInfo, rate,
                    openRate,
                    closeRate, upRate);
                info += "第" + (i + 1) + "天" + sellInfo + ",收益=" + rate + ",开盘收益=" + openRate + ",收盘收益=" + closeRate
                    + ",最高点收益=" + upRate;
                break;
            }
            //120个点以上特殊处理
            if (BigDecimalUtils.isGe(realtimeRate, new BigDecimal(120))) {
                Integer yesterdayTradeNumTenTimes = yesterdayDayData.getTradeNum() * 10;
                if (data.getTradeNum() >= yesterdayTradeNumTenTimes) {
                    sellInfo = "出现十倍信号量,止盈";
                    log.debug("第{}天-{}-{}-{}，收益={}，开盘收益={},收盘收益={},最高点收益={}", (i + 1), code, data.getDate(), sellInfo,
                        rate, openRate, closeRate, upRate);
                    info += "第" + (i + 1) + "天" + sellInfo + ",收益=" + rate + ",开盘收益=" + openRate + ",收盘收益=" + closeRate
                        + ",最高点收益=" + upRate;
                    break;
                }
            }
            //50个点以上特殊处理
            if (BigDecimalUtils.isGe(realtimeRate, new BigDecimal(50))) {
                Integer yesterdayTradeNum = yesterdayDayData.getTradeNum() * 10;
                if (data.getTradeNum() >= yesterdayTradeNum) {
                    sellInfo = "出现十倍信号量,止盈";
                    log.debug("第{}天-{}-{}-{}，收益={}，开盘收益={},收盘收益={},最高点收益={}", (i + 1), code, data.getDate(), sellInfo,
                        rate,
                        openRate,
                        closeRate, upRate);
                    info += "第" + (i + 1) + "天" + sellInfo + ",收益=" + rate + ",开盘收益=" + openRate + ",收盘收益=" + closeRate
                        + ",最高点收益=" + upRate;
                    break;
                }
                Integer yesterdayTradeNumThreeTimes = yesterdayDayData.getTradeNum() * 3;
                if (data.getTradeNum() >= yesterdayTradeNumThreeTimes && BigDecimalUtils.isLe(data.getRate(),
                    new BigDecimal(-5))) {
                    sellInfo = "出现3倍信号量且暴跌,止盈";
                    log.debug("第{}天-{}-{}-{}，收益={}，开盘收益={},收盘收益={},最高点收益={}", (i + 1), code, data.getDate(), sellInfo,
                        rate, openRate, closeRate, upRate);
                    info += "第" + (i + 1) + "天" + sellInfo + ",收益=" + rate + ",开盘收益=" + openRate + ",收盘收益=" + closeRate
                        + ",最高点收益=" + upRate;
                    break;
                }
            }
            //最高盈利30个点以上处理
            if (BigDecimalUtils.isGe(rangeMaxRate, new BigDecimal(30))) {
                if (BigDecimalUtils.isGe(fallBackRate, FIFTEEN)) {
                    sellInfo = "回落2成,卖出";
                    log.debug("第{}天-{}-{}-{}，收益={}，开盘收益={},收盘收益={},最高点收益={}", (i + 1), code, data.getDate(), sellInfo,
                        rate,
                        openRate, closeRate, upRate);
                    info += "第" + (i + 1) + "天" + sellInfo + ",收益=" + rate + ",开盘收益=" + openRate + ",收盘收益=" + closeRate
                        + ",最高点收益=" + upRate;
                    break;
                }
            } else if (BigDecimalUtils.isGe(rangeMaxRate, new BigDecimal(20))) {
                BigDecimal limitRate = rangeMaxRate.multiply(new BigDecimal(0.3));
                if (BigDecimalUtils.isGe(fallBackRate, limitRate)) {
                    sellInfo = "回落3成,卖出";
                    log.debug("第{}天-{}-{}-{}，收益={}，开盘收益={},收盘收益={},最高点收益={}", (i + 1), code, data.getDate(), sellInfo,
                        rate,
                        openRate, closeRate, upRate);
                    info += "第" + (i + 1) + "天" + sellInfo + ",收益=" + rate + ",开盘收益=" + openRate + ",收盘收益=" + closeRate
                        + ",最高点收益=" + upRate;
                    break;
                }
            } else if (i > 2 && BigDecimalUtils.isGe(rangeMaxRate, new BigDecimal(10))) {
                BigDecimal limitRate = rangeMaxRate.multiply(new BigDecimal(0.7));
                if (BigDecimalUtils.isGe(fallBackRate, limitRate)) {
                    //回落超过3成
                    sellInfo = "10个点回7成,卖出";
                    log.debug("第{}天-{}-{}-{}，收益={}，开盘收益={},收盘收益={},最高点收益={}", (i + 1), code, data.getDate(), sellInfo,
                        rate,
                        openRate, closeRate, upRate);
                    info += "第" + (i + 1) + "天" + sellInfo + ",收益=" + rate + ",开盘收益=" + openRate + ",收盘收益=" + closeRate
                        + ",最高点收益=" + upRate;
                    break;
                }
            } else if (i > 2 && BigDecimalUtils.isGe(rangeMaxRate, new BigDecimal(7))
                && BigDecimalUtils.isLe(rate, BigDecimal.ZERO)) {
                //从7个点跌到负数
                sellInfo = "从7个点跌到负数,卖出";
                log.debug("第{}天-{}-{}-{}，收益={}，开盘收益={},收盘收益={},最高点收益={}", (i + 1), code, data.getDate(), sellInfo,
                    rate,
                    openRate, closeRate, upRate);
                info += "第" + (i + 1) + "天" + sellInfo + ",收益=" + rate + ",开盘收益=" + openRate + ",收盘收益=" + closeRate
                    + ",最高点收益=" + upRate;
                break;
            }
            if (i == subList.size() - 1) {
                log.debug("第{}天-{}-{}-{}，收益={}，开盘收益={},收盘收益={},最高点收益={}", (i + 1), code, data.getDate(), sellInfo, rate,
                    openRate,
                    closeRate, upRate);
                info += "第" + (i + 1) + "天" + sellInfo + ",收益=" + rate + ",开盘收益=" + openRate + ",收盘收益=" + closeRate
                    + ",最高点收益=" + upRate;
                break;
            }

        }
        strategyTradeDTO.setHoldDay(holdIndex + 1);
        strategyTradeDTO.setSellDate(DateUtil.parseDate(sellDate));
        strategyTradeDTO.setSellPrice(sellPrice);
        strategyTradeDTO.setProfitRate(rate);
        strategyTradeDTO.setRangeMaxPrice(rangeMaxPrice);
        strategyTradeDTO.setRealtimeRate(BigDecimalUtils.subStractAndDividePrecent(rangeMaxPrice, buyPrice));
        strategyTradeDTO.setStrategyDescription(info);
        strategyTradeDTO.setStatus("sell");
        strategyTradeDTO.setWinFlag(BigDecimalUtils.isGt(rate, BigDecimal.ZERO) ? "y" : "n");
    }
}
